<template>
    <div
        class="relative"
        @mouseenter="onMouseenter"
        @mouseleave="onMouseleave"
    >
        <div ref="referenceTarget">
            <!-- 具名插槽：触发弹层的视图 -->
            <slot name="reference" />
        </div>
        <!-- 气泡展示 -->
        <transition name="slide">
            <div
                v-show="isVisible"
                ref="contentTarget"
                :style="contentStyle"
                class="absolute p-1 z-20 bg-white border rounded-md dark:bg-zinc-900 dark:border-zinc-700"
            >
                <slot />
                <!-- 匿名插槽：弹出层视图中展示的内容 -->
            </div>
        </transition>
    </div>
</template>
<script>
// 1、指定所有可选位置的常量，并生成enum
const PROP_TOP_LEFT = 'top-left'
const PROP_TOP_RIGHT = 'top-right'
const PROP_BOTTOM_LEFT = 'bottom-left'
const PROP_BOTTOM_RIGHT = 'bottom-right'
// 定义指定位置的 Enum
const placementEnum = [PROP_TOP_LEFT, PROP_TOP_RIGHT, PROP_BOTTOM_LEFT, PROP_BOTTOM_RIGHT]

//延迟关闭时间
const DELAY_TIME = 100
</script>

<script setup>
/**
 * 1、指定所有可选位置的常量，并生成enum
 * 2、通过props控制指定位置
 * 3、获取元素的Dom。创建读取元素尺寸的方法
 * 4、生成气泡的样式对象，用来控制每个位置对应的样式
 * 5、根据prop，计算样式对象
 */
import { nextTick, ref, watch } from 'vue'

//-----------2、通过props控制指定位置  --------------------
const props = defineProps({
    //控制气泡弹出位置，给出开发者错误提示
    placement: {
        type: String,
        default: PROP_TOP_LEFT,
        validator(val) {
            const result = placementEnum.includes(val)
            if (!result) {
                throw new Error(`你的 placement 必须是 ${placementEnum.join('、')}中的一个`)
            }
            return result
        }
    }
})

// ---------------控制显示隐藏 -----------------------------------------
const isVisible = ref(false)
// 控制鼠标移入到缝隙中，popover消失问题
let timer = null
// 鼠标移入触发
const onMouseenter = () => {
    isVisible.value = true
    if (timer) {
        clearTimeout(timer)
    }
}
// 鼠标移出 触发
const onMouseleave = () => {
    timer = setTimeout(() => {
        isVisible.value = false
        timer = null
    }, DELAY_TIME)
}

//---------------获取元素的Dom。创建读取元素尺寸的方法------------------------
const referenceTarget = ref(null)
const contentTarget = ref(null)
const useElementSize = (target) => {
    if (!target) return {}
    return {
        width: target.value.offsetWidth,
        height: target.value.offsetHeight
    }
}

//---------------气泡的样式对象，控制气泡展示的位置------------------------------
const contentStyle = ref({
    top: 0,
    left: 0
})
// 计算：期望气泡展示的时候再进行计算
watch(isVisible, (val) => {
    if (!val) return
    nextTick(() => {
        switch (props.placement) {
            case PROP_TOP_LEFT:
                contentStyle.value.top = 0
                contentStyle.value.left = -useElementSize(contentTarget).width + 'px'
                break
            case PROP_TOP_RIGHT:
                contentStyle.value.top = 0
                contentStyle.value.left = useElementSize(referenceTarget).width + 'px'
                break
            case PROP_BOTTOM_LEFT:
                contentStyle.value.top = useElementSize(referenceTarget).height + 'px'
                contentStyle.value.left = -useElementSize(contentTarget).width + 'px'
                break
            case PROP_BOTTOM_RIGHT:
                contentStyle.value.top = useElementSize(referenceTarget).height + 'px'
                contentStyle.value.left = useElementSize(referenceTarget).width + 'px'
                break
        }
    })
})
</script>
<style lang="scss" scope>
.slide-enter-active,
.slide-leave-active {
    transition: opacity 0.3s, transform 0.3s;
}
.slide-enter-from,
.slide-leave-to {
    opacity: 0;
    transform: translateY(20px);
}
</style>
